package org.jordens.jdbcspy.sync;

import org.jordens.jdbcspy.jmx.*;

import java.io.*;
import java.util.*;
import java.util.regex.*;

/**
 * The LogFileDataStream is a DataStream implementation that receives its data
 * from a JDBCSpy log file instead of the bean itself (as the JMXDataStream does).
 *
 * @author Adam Jordens
 */
public class LogFileDataStreamer extends AbstractDataStreamer
{
    private static final String LOG_FILE_REGEX = "(\\d+)\\s-\\s\\[(.+)\\]\\s-\\s(\\d+)ms\\s-\\s";
    private static final Pattern LOG_FILE_REGEX_PATTERN  = Pattern.compile(LOG_FILE_REGEX);

    private final Timer refreshTimer = new Timer();

    private final List<JDBCSpyRecord> jdbcSpyRecords;

    /**
     * @param logFile JDBCSpy log file
     * @param period Period of time (milliseconds) to consider as a time-slice for notification purposes
     */
    public LogFileDataStreamer(File logFile, long period)
    {
        jdbcSpyRecords = parseLogFile(logFile, period);
    }

    /**
     * {@inheritDoc}
     */
    public void notifyDataUpdaters()
    {
        JDBCSpyRecord updatedRecord = null;
        if (jdbcSpyRecords.size() > 0)
        {
            updatedRecord = jdbcSpyRecords.get(0);
        }
        else
        {
            return;
        }

        try
        {
            for (DataUpdater dataUpdater : getRegisteredDataUpdaters())
            {
                dataUpdater.update(
                        Collections.singletonList(updatedRecord));
            }
        }
        finally
        {
            jdbcSpyRecords.remove(updatedRecord);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void start()
    {
        refreshTimer.schedule(new RefreshTask(), new Date(), 1000);
    }

    /**
     * @param logFile JDBCSpy log file to parse
     * @param period Period of time (milliseconds) to consider as a time-slice for notification purposes
     * @return JDBCSpyRecords representing the activity logged in 'logFile'
     */
    private List<JDBCSpyRecord> parseLogFile(File logFile, long period)
    {
        List<JDBCSpyRecord> jdbcSpyRecords = new ArrayList<JDBCSpyRecord>();

        Map<String, QueryStatistic> queryStatistics =
                new HashMap<String, QueryStatistic>();

        List<QueryStatistic> updatedQueryStatistics = new ArrayList<QueryStatistic>();

        BufferedReader reader = null;
        try
        {
            long startMS = 0;

            long queryCount = 0;
            long sqlExecutionTime = 0;

            reader = new BufferedReader(new FileReader(logFile));
            while (reader.ready())
            {
                String record = reader.readLine();

                Matcher matcher = LOG_FILE_REGEX_PATTERN.matcher(record);
                if (matcher.find())
                {
                    if (startMS == 0)
                    {
                        startMS = Long.valueOf(matcher.group(1));
                    }

                    long currentMS = Long.valueOf(matcher.group(1));
                    String currentSQL = matcher.group(2);
                    long currentExecutionTime = Long.valueOf(matcher.group(3));

                    if (currentMS >= (startMS + period))
                    {
                        // we're reached the end of the time period
                        // add an entry and reset boundaries before restarting
                        jdbcSpyRecords.add(
                                new JDBCSpyRecord(
                                        updatedQueryStatistics,
                                        sqlExecutionTime,
                                        queryCount));

                        queryCount = 0;
                        sqlExecutionTime = 0;
                        updatedQueryStatistics = new ArrayList<QueryStatistic>();
                        startMS = currentMS;
                    }

                    // we're still within the same 1s period
                    queryCount++;
                    sqlExecutionTime += currentExecutionTime;

                    QueryStatistic queryStatistic = queryStatistics.get(currentSQL);
                    if (queryStatistic == null)
                    {
                        queryStatistic =
                                new QueryStatistic(
                                        currentSQL, currentExecutionTime, currentMS);
                    }
                    else
                    {
                        queryStatistic = new QueryStatistic(queryStatistic);
                        queryStatistic.record(currentExecutionTime);
                    }

                    queryStatistics.put(currentSQL, queryStatistic);
                    updatedQueryStatistics.add(queryStatistic);
                }
            }

            return jdbcSpyRecords;
        }
        catch (Exception e)
        {
            throw new IllegalStateException(e);
        }
        finally
        {
            if (reader != null)
            {
                try
                {
                    reader.close();
                }
                catch (Exception e)
                {
                    // do nothing
                }
            }
        }
    }

    /**
     * Basic task to notify all DataUpdaters. 
     */
    private class RefreshTask extends TimerTask
    {
        /**
         * {@inheritDoc}
         */
        public void run()
        {
            notifyDataUpdaters();
        }
    }
}
